1use crate::load_wide_string;
2
3pub struct ProjectFile<'a> {
5 pub(crate) internal: *mut aviutl2_sys::plugin2::PROJECT_FILE,
6 _marker: std::marker::PhantomData<&'a ()>,
7}
8
9#[derive(thiserror::Error, Debug)]
11pub enum ProjectFileError {
12 #[error("key contains null byte: {0}")]
13 KeyContainsNull(std::ffi::NulError),
14 #[error("data retrieval failed for key {0}")]
15 RetrievalFailed(String),
16 #[error("data length exceeds 4096 bytes, got {0} bytes")]
17 DataTooLarge(usize),
18 #[error("value contains null byte: {0}")]
19 ValueContainsNull(std::ffi::NulError),
20}
21
22impl<'a> ProjectFile<'a> {
23 pub unsafe fn from_raw(raw: *mut aviutl2_sys::plugin2::PROJECT_FILE) -> Self {
29 Self {
30 internal: raw,
31 _marker: std::marker::PhantomData,
32 }
33 }
34
35 pub fn get_param_string(&self, key: &str) -> Result<String, ProjectFileError> {
42 let c_key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
43 unsafe {
44 let raw_str = ((*self.internal).get_param_string)(c_key.as_ptr() as _);
45 if raw_str.is_null() {
46 return Err(ProjectFileError::RetrievalFailed(key.to_string()));
47 }
48 Ok(std::ffi::CStr::from_ptr(raw_str)
49 .to_string_lossy()
50 .into_owned())
51 }
52 }
53
54 pub fn get_param_binary(&self, key: &str, data: &mut [u8]) -> Result<(), ProjectFileError> {
62 let success = unsafe {
63 let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
64 ((*self.internal).get_param_binary)(
65 key.as_ptr() as _,
66 data.as_mut_ptr() as _,
67 data.len() as _,
68 )
69 };
70 if !success {
71 return Err(ProjectFileError::RetrievalFailed(key.to_string()));
72 }
73 Ok(())
74 }
75
76 pub fn set_param_string(&mut self, key: &str, value: &str) -> Result<(), ProjectFileError> {
82 let key_cstr = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
83 let value_cstr =
84 std::ffi::CString::new(value).map_err(ProjectFileError::ValueContainsNull)?;
85 unsafe {
86 ((*self.internal).set_param_string)(key_cstr.as_ptr() as _, value_cstr.as_ptr() as _);
87 }
88 Ok(())
89 }
90
91 pub fn set_param_binary(&mut self, key: &str, data: &[u8]) -> Result<(), ProjectFileError> {
98 if data.len() > 4096 {
99 return Err(ProjectFileError::DataTooLarge(data.len()));
100 }
101 unsafe {
102 let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
103 ((*self.internal).set_param_binary)(
104 key.as_ptr() as _,
105 data.as_ptr() as _,
106 data.len() as _,
107 );
108 }
109 Ok(())
110 }
111
112 pub fn clear_params(&mut self) {
114 unsafe { ((*self.internal).clear_params)() }
115 }
116
117 pub fn get_path(&self) -> Option<std::path::PathBuf> {
119 unsafe {
120 let raw_str = ((*self.internal).get_project_file_path)();
121 if raw_str.is_null() {
122 return None;
123 }
124 let path_str = load_wide_string(raw_str);
125 if path_str.is_empty() {
126 return None;
127 }
128 Some(std::path::PathBuf::from(path_str))
129 }
130 }
131}
132
133#[cfg(feature = "serde")]
134const _: () = {
135 use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
136 use std::io::Read;
137
138 static NAMESPACE: &str = "--aviutl2-rs";
139 const BINARY_CHUNK_SIZE: usize = 4096;
140 const BASE64_CHUNK_RAW_SIZE: usize = 3072;
142
143 #[derive(thiserror::Error, Debug)]
145 pub enum ProjectFileSerdeError {
146 #[error("serialization error: {0}")]
147 Serialization(#[from] rmp_serde::encode::Error),
148 #[error("deserialization error: {0}")]
149 Deserialization(#[from] rmp_serde::decode::Error),
150 #[error("base64 decode error: {0}")]
151 Base64Decode(#[from] base64::DecodeError),
152 #[error("zstd dompression error: {0}")]
153 Decompression(#[from] std::io::Error),
154 #[error("zlib error: {0:?}")]
155 Zlib(zlib_rs::ReturnCode),
156 #[error("project file error: {0}")]
157 ProjectFile(#[from] ProjectFileError),
158 #[error("unsupported serialization format")]
159 UnsupportedFormat,
160 #[error("invalid header format: {0}")]
161 InvalidHeaderFormat(String),
162 #[error("incomplete data retrieved for key")]
163 IncompleteData,
164 }
165
166 impl<'a> ProjectFile<'a> {
167 pub fn serialize<T: serde::Serialize>(
177 &mut self,
178 key: &str,
179 value: &T,
180 ) -> Result<(), ProjectFileSerdeError> {
181 self.delete_old_chunks(key)?;
182 let base_bytes = rmp_serde::to_vec_named(value)?;
183 let compressed_bound = zlib_rs::compress_bound(base_bytes.len());
184 let mut compressed_bytes = vec![0u8; compressed_bound];
185 let (compressed_bytes, result) = zlib_rs::compress_slice(
186 &mut compressed_bytes,
187 &base_bytes,
188 zlib_rs::DeflateConfig::default(),
189 );
190 if result != zlib_rs::ReturnCode::Ok {
191 return Err(ProjectFileSerdeError::Zlib(result));
192 }
193 let num_bytes = compressed_bytes.len();
194 if num_bytes > base_bytes.len() {
195 self.set_param_string(
197 key,
198 &format!("{NAMESPACE}:serde-rmp-base64-v1:{}", base_bytes.len()),
199 )?;
200 for (i, chunk) in base_bytes.chunks(BASE64_CHUNK_RAW_SIZE).enumerate() {
201 let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
202 self.set_param_string(&chunk_key, &BASE64.encode(chunk))?;
203 }
204 return Ok(());
205 }
206 self.set_param_string(
207 key,
208 &format!(
209 "{NAMESPACE}:serde-rmp-base64-zlib-v1:{},{}",
210 num_bytes,
211 base_bytes.len()
212 ),
213 )?;
214 for (i, chunk) in compressed_bytes.chunks(BASE64_CHUNK_RAW_SIZE).enumerate() {
215 let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
216 self.set_param_string(&chunk_key, &BASE64.encode(chunk))?;
217 }
218 Ok(())
219 }
220
221 pub fn deserialize<T: serde::de::DeserializeOwned>(
223 &self,
224 key: &str,
225 ) -> Result<T, ProjectFileSerdeError> {
226 let header = self.get_param_string(key)?;
227 if let Ok(value) = self.decode_serde_rmp_base64_v1(key, &header) {
228 return Ok(value);
229 }
230 if let Ok(value) = self.decode_serde_zstd_v1(key, &header) {
231 return Ok(value);
232 }
233 if let Ok(value) = self.decode_serde_rmp_v1(key, &header) {
234 return Ok(value);
235 }
236 if let Ok(value) = self.decode_serde_rmp_base64_zlib_v1(key, &header) {
237 return Ok(value);
238 }
239 Err(ProjectFileSerdeError::UnsupportedFormat)
240 }
241
242 fn decode_serde_rmp_base64_v1<T: serde::de::DeserializeOwned>(
243 &self,
244 key: &str,
245 header: &str,
246 ) -> Result<T, ProjectFileSerdeError> {
247 let header_prefix = format!("{NAMESPACE}:serde-rmp-base64-v1:");
248 let num_bytes = header
249 .strip_prefix(&header_prefix)
250 .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
251 let num_bytes: usize = num_bytes
252 .parse()
253 .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
254 if num_bytes == 0 {
255 return Err(ProjectFileSerdeError::InvalidHeaderFormat(
256 header.to_string(),
257 ));
258 }
259 let chunks = self.collect_base64_chunks(num_bytes, key)?;
260 let value: T = rmp_serde::from_slice(&chunks)?;
261 Ok(value)
262 }
263
264 fn decode_serde_rmp_v1<T: serde::de::DeserializeOwned>(
265 &self,
266 key: &str,
267 header: &str,
268 ) -> Result<T, ProjectFileSerdeError> {
269 let header_prefix = format!("{NAMESPACE}:serde-rmp-v1:");
270 let num_bytes = header
271 .strip_prefix(&header_prefix)
272 .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
273 let num_bytes: usize = num_bytes
274 .parse()
275 .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
276 if num_bytes == 0 {
277 return Err(ProjectFileSerdeError::InvalidHeaderFormat(
278 header.to_string(),
279 ));
280 }
281 let chunks = self.collect_chunks(num_bytes, key)?;
282 let value: T = rmp_serde::from_slice(&chunks)?;
283 Ok(value)
284 }
285
286 fn decode_serde_rmp_base64_zlib_v1<T: serde::de::DeserializeOwned>(
287 &self,
288 key: &str,
289 header: &str,
290 ) -> Result<T, ProjectFileSerdeError> {
291 let header_prefix = format!("{NAMESPACE}:serde-rmp-base64-zlib-v1:");
292 let num_bytes = header
293 .strip_prefix(&header_prefix)
294 .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
295 let (compressed_size, uncompressed_size) =
296 num_bytes
297 .split_once(',')
298 .ok_or(ProjectFileSerdeError::InvalidHeaderFormat(
299 header.to_string(),
300 ))?;
301 let compressed_size: usize = compressed_size
302 .parse()
303 .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
304 let uncompressed_size: usize = uncompressed_size
305 .parse()
306 .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
307 if compressed_size == 0 || uncompressed_size == 0 {
308 return Err(ProjectFileSerdeError::InvalidHeaderFormat(
309 header.to_string(),
310 ));
311 }
312 let compressed_chunks = self.collect_base64_chunks(compressed_size, key)?;
313 let mut decompressed_bytes = vec![0u8; uncompressed_size];
314 let (decompressed_bytes, result) = zlib_rs::decompress_slice(
315 &mut decompressed_bytes,
316 &compressed_chunks,
317 zlib_rs::InflateConfig::default(),
318 );
319 if result != zlib_rs::ReturnCode::Ok {
320 return Err(ProjectFileSerdeError::Zlib(result));
321 }
322 let value: T = rmp_serde::from_slice(decompressed_bytes)?;
323 Ok(value)
324 }
325
326 fn collect_chunks(
327 &self,
328 num_bytes: usize,
329 key: &str,
330 ) -> Result<Vec<u8>, ProjectFileSerdeError> {
331 let mut bytes = Vec::with_capacity(num_bytes);
332 let mut read_bytes = 0;
333 let mut chunk = vec![0u8; BINARY_CHUNK_SIZE];
334 for i in 0.. {
335 let chunk_key = format!("{NAMESPACE}:serde-chunk:{}:{}", key, i);
336 let to_read = std::cmp::min(BINARY_CHUNK_SIZE, num_bytes - read_bytes);
337 chunk.resize(to_read, 0);
338 match self.get_param_binary(&chunk_key, &mut chunk) {
339 Ok(()) => {
340 bytes.extend_from_slice(&chunk);
341 read_bytes += to_read;
342 if read_bytes >= num_bytes {
343 break;
344 }
345 }
346 Err(_) => break,
347 }
348 }
349 if read_bytes != num_bytes {
350 return Err(ProjectFileSerdeError::IncompleteData);
351 }
352 Ok(bytes)
353 }
354
355 fn collect_base64_chunks(
356 &self,
357 num_bytes: usize,
358 key: &str,
359 ) -> Result<Vec<u8>, ProjectFileSerdeError> {
360 let mut bytes = Vec::with_capacity(num_bytes);
361 for i in 0.. {
362 let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
363 let chunk = match self.get_param_string(&chunk_key) {
364 Ok(chunk) => chunk,
365 Err(_) => break,
366 };
367 let chunk = BASE64.decode(chunk)?;
368 bytes.extend_from_slice(&chunk);
369 if bytes.len() >= num_bytes {
370 break;
371 }
372 }
373 if bytes.len() != num_bytes {
374 return Err(ProjectFileSerdeError::IncompleteData);
375 }
376 Ok(bytes)
377 }
378
379 fn decode_serde_zstd_v1<T: serde::de::DeserializeOwned>(
380 &self,
381 key: &str,
382 header: &str,
383 ) -> Result<T, ProjectFileSerdeError> {
384 let header_prefix = format!("{NAMESPACE}:serde-zstd-v1:");
385 let num_bytes = header
386 .strip_prefix(&header_prefix)
387 .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
388 let num_bytes: usize = num_bytes
389 .parse()
390 .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
391 if num_bytes == 0 {
392 return Err(ProjectFileSerdeError::InvalidHeaderFormat(
393 header.to_string(),
394 ));
395 }
396 let mut bytes = Vec::with_capacity(num_bytes);
397 let mut read_bytes = 0;
398 let mut chunk = vec![0u8; BINARY_CHUNK_SIZE];
399 for i in 0.. {
400 let chunk_key = format!("{NAMESPACE}:serde-zstd-v1:chunk:{}:{}", key, i);
401 let to_read = std::cmp::min(BINARY_CHUNK_SIZE, num_bytes - read_bytes);
402 chunk.resize(to_read, 0);
403 match self.get_param_binary(&chunk_key, &mut chunk) {
404 Ok(()) => {
405 bytes.extend_from_slice(&chunk);
406 read_bytes += to_read;
407 if read_bytes >= num_bytes {
408 break;
409 }
410 }
411 Err(_) => break,
412 }
413 }
414 if read_bytes != num_bytes {
415 return Err(ProjectFileSerdeError::IncompleteData);
416 }
417 let mut decoder = ruzstd::decoding::StreamingDecoder::new(&bytes[..])
418 .map_err(|e| ProjectFileSerdeError::Decompression(std::io::Error::other(e)))?;
419 let mut decompressed_bytes = vec![];
420 decoder.read_to_end(&mut decompressed_bytes)?;
421 let value: T = rmp_serde::from_slice(&decompressed_bytes)?;
422 Ok(value)
423 }
424
425 fn delete_old_chunks(&mut self, key: &str) -> Result<(), ProjectFileSerdeError> {
426 for i in 0.. {
427 let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
428 match self.get_param_string(&chunk_key) {
429 Ok(_) => {
430 self.set_param_string(&chunk_key, "")?;
431 }
432 Err(_) => break,
433 }
434 }
435 for i in 0.. {
436 let chunk_key = format!("{NAMESPACE}:serde-chunk:{}:{}", key, i);
437 match self.get_param_binary(&chunk_key, &mut [0u8; 1]) {
438 Ok(_) => {
439 self.set_param_string(&chunk_key, "")?;
440 }
441 Err(_) => break,
442 }
443 }
444 for i in 0.. {
445 let chunk_key = format!("{NAMESPACE}:serde-zstd-v1:chunk:{}:{}", key, i);
446 match self.get_param_binary(&chunk_key, &mut [0u8; 1]) {
447 Ok(_) => {
448 self.set_param_string(&chunk_key, "")?;
449 }
450 Err(_) => break,
451 }
452 }
453 for i in 0.. {
454 let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
455 match self.get_param_string(&chunk_key) {
456 Ok(_) => {
457 self.set_param_string(&chunk_key, "")?;
458 }
459 Err(_) => break,
460 }
461 }
462 Ok(())
463 }
464 }
465};